using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using Cysharp.Threading.Tasks;
using Google.Protobuf;
using MoonFramework.Scripts.Tool.Log;
using UnityEditor;
using UnityEngine;

namespace MoonFramework.Scripts.Tool.DataSaveManager
{
#if UNITY_EDITOR
    public static class SaveDataUtility
    {
        private const string SaveDirName = "saveData";
        private const string SettingDirName = "settingData";

        private static readonly string SaveDirPath = Path.Combine(Application.persistentDataPath, SaveDirName);
        private static readonly string SettingDirPath = Path.Combine(Application.persistentDataPath, SettingDirName);

        static SaveDataUtility()
        {
            EnsureDirectoryExists(SaveDirPath);
            EnsureDirectoryExists(SettingDirPath);
        }

        private static void EnsureDirectoryExists(string path)
        {
            if (!Directory.Exists(path))
                Directory.CreateDirectory(path);
        }
        
        [MenuItem("MoonFramework/Open Save Folder")]
        public static void OpenSaveFolder() => OpenFolder(SaveDirPath);

        [MenuItem("MoonFramework/Open Settings Folder")]
        public static void OpenSettingsFolder() => OpenFolder(SettingDirPath);

        private static void OpenFolder(string path)
        {
            if (Directory.Exists(path))
                Process.Start("explorer.exe", path.Replace("/", "\\"));
            else
                Serilog.Log.Error($"Folder not found: {path}");
        }
    }
#endif

    public class SaveItem
    {
        public int SaveID { get; private set; }
        public DateTime LastSaveTime { get; private set; }

        public SaveItem(int saveID, DateTime lastSaveTime)
        {
            SaveID = saveID;
            LastSaveTime = lastSaveTime;
        }

        public void UpdateTime(DateTime lastSaveTime)
        {
            LastSaveTime = lastSaveTime;
        }
    }

    public static class SaveManager
    {
        private static readonly string SaveDirPath = Path.Combine(Application.persistentDataPath, "saveData");
        private static readonly string SettingDirPath = Path.Combine(Application.persistentDataPath, "settingData");

        private static SaveManagerData _saveData;
        private static SaveManagerDataPro _saveDataPro;
        private static readonly Dictionary<int, Dictionary<string, object>> _cache = new();
        
        private class SaveManagerData
        {
            public int CurID;
            public readonly Dictionary<int, SaveItem> SaveItems = new();
        }
        
        static SaveManager()
        {
            EnsureDirectoryExists(SaveDirPath);
            EnsureDirectoryExists(SettingDirPath);
            InitSaveManagerData(false).Forget();
        }

        private static void EnsureDirectoryExists(string path)
        {
            if (!Directory.Exists(path))
                Directory.CreateDirectory(path);
        }

        private static async UniTaskVoid InitSaveManagerData(bool isAsync = true)
        {
            _saveDataPro = isAsync ? await LoadFileAsync<SaveManagerDataPro>(Path.Combine(SaveDirPath, "SaveManagerData")) 
                                    : LoadFile<SaveManagerDataPro>(Path.Combine(SaveDirPath, "SaveManagerData"));
            if (_saveDataPro == null)
            {
                _saveDataPro = new SaveManagerDataPro();
                _saveData = new SaveManagerData();
                UpdateSaveManagerData();
            }
            else
            {
                _saveData = new SaveManagerData
                {
                    CurID = _saveDataPro.CurID
                };
                foreach (var saveItemPro in _saveDataPro.SaveItems)
                {
                    int key = saveItemPro.Key;
                    _saveData.SaveItems.Add(key, new SaveItem(saveItemPro.Value.SaveID, new DateTime(saveItemPro.Value.LastSaveTime, DateTimeKind.Utc)));
                }
            }
        }

        private static void UpdateSaveManagerData()
        {
            _saveDataPro.CurID = _saveData.CurID;
            foreach (var kvp in _saveData.SaveItems)
            {
                _saveDataPro.SaveItems[kvp.Key] = new SaveItemPro
                {
                    SaveID = kvp.Value.SaveID,
                    LastSaveTime = kvp.Value.LastSaveTime.ToUniversalTime().Ticks
                };
            }

            SaveFile(_saveDataPro, Path.Combine(SaveDirPath, "SaveManagerData"));
        }

        public static Dictionary<int, SaveItem> GetAllSaveItems() => _saveData.SaveItems;

        public static SaveItem GetSaveItem(int id) => _saveData.SaveItems.ContainsKey(id) ? _saveData.SaveItems[id] : null;

        public static int CreateSaveItem()
        {
            var newSaveID = _saveData.CurID;
            var newSaveItem = new SaveItem(newSaveID, DateTime.Now);
            _saveData.SaveItems.Add(newSaveID, newSaveItem);
            _saveDataPro.SaveItems.Add(newSaveID, new SaveItemPro
            {
                SaveID = newSaveItem.SaveID,
                LastSaveTime = newSaveItem.LastSaveTime.ToUniversalTime().Ticks
            });
            _saveData.CurID ++;
            return _saveData.CurID;
        }

        public static void DeleteSaveItem(int saveID)
        {
            string itemDir = GetSavePath(saveID);
            if (!string.IsNullOrEmpty(itemDir) && Directory.Exists(itemDir))
            {
                Directory.Delete(itemDir, true);
            }
            _cache.Remove(saveID);
            if(_saveData.CurID == saveID + 1)
                _saveData.CurID = Math.Max(0, _saveData.CurID - 1);
            _saveData.SaveItems.Remove(saveID);
            _saveDataPro.CurID = _saveData.CurID;
            _saveDataPro.SaveItems.Remove(saveID);
            SaveFile(_saveDataPro, Path.Combine(SaveDirPath, "SaveManagerData"));
        }

        private static string GetSavePath(int saveID, bool createDir = true)
        {
            if (!_saveData.SaveItems.ContainsKey(saveID)) return null;
            string saveDir = Path.Combine(SaveDirPath, saveID.ToString());
            
            //确定某个文件夹是否存在
            if (Directory.Exists(saveDir) == false)
            {
                if (createDir)
                    Directory.CreateDirectory(saveDir);
                else
                    return null;
            }
            return saveDir;
        }

        public static void SaveObject(IMessage saveObject, string saveFileName, int saveID = 0)
        {
            string savePath = Path.Combine(GetSavePath(saveID), saveFileName);
            SaveFile(saveObject, savePath);
            GetSaveItem(saveID).UpdateTime(DateTime.Now);
            UpdateSaveManagerData();
            SetCache(saveID, saveFileName, saveObject);
        }

        public static void SaveObject(this IMessage saveObject, int saveID = 0) => SaveObject(saveObject, saveObject.GetType().Name, saveID);

        public static T LoadObject<T>(string saveFileName, int saveID = 0) where T : class, IMessage, new()
        {
            T cachedObject = GetCache<T>(saveID, saveFileName);
            if (cachedObject == null)
            {
                string savePath = Path.Combine(GetSavePath(saveID), saveFileName);
                cachedObject = LoadFile<T>(savePath);
                SetCache(saveID, saveFileName, cachedObject);
            }
            return cachedObject;
        }

        public static T LoadObject<T>(int saveID = 0) where T : class, IMessage, new()  => LoadObject<T>(typeof(T).Name, saveID);

        public static async UniTask<T> LoadObjectAsync<T>(string saveFileName, int saveID = 0) where T : class, IMessage, new()
        {
            T cachedObject = GetCache<T>(saveID, saveFileName);
            if (cachedObject == null)
            {
                string savePath = Path.Combine(GetSavePath(saveID), saveFileName);
                cachedObject = await LoadFileAsync<T>(savePath);
                SetCache(saveID, saveFileName, cachedObject);
            }
            return cachedObject;
        }
        public static async UniTask<T> LoadObjectAsync<T>(int saveID = 0)  where T : class, IMessage, new() => await LoadObjectAsync<T>(typeof(T).Name, saveID);
        
        public static async UniTask<T> LoadSettingAsync<T>(string fileName) where T : class, IMessage, new()
            => await LoadFileAsync<T>(Path.Combine(SettingDirPath, fileName));

        public static void SaveSetting(IMessage saveObject, string fileName) 
            => SaveFile(saveObject, Path.Combine(SettingDirPath, fileName));

        private static void SetCache(int saveID, string fileName, object saveObj)
        {
            if (!_cache.TryGetValue(saveID, out var saveDic))
            {
                saveDic = new Dictionary<string, object>();
                _cache[saveID] = saveDic;
            }
            saveDic[fileName] = saveObj;
        }

        private static T GetCache<T>(int saveID, string fileName) where T : class
        {
            if (_cache.TryGetValue(saveID, out var saveList) && saveList.TryGetValue(fileName, out var saveObj))
                return saveObj as T;
            return null;
        }

        public static void SaveFile(IMessage message, string path)
        {
            using (FileStream fs = File.Create(path))
            {
                message.WriteTo(fs);
            }
        }

        public static T LoadFile<T>(string path) where T : class, IMessage, new()
        {
            if (!File.Exists(path)) return null;
            byte[] bytes = File.ReadAllBytes(path);
            return Deserialize<T>(bytes);
        }

        public static async UniTask<T> LoadFileAsync<T>(string path) where T : class, IMessage, new()
        {
            if (!File.Exists(path)) return null;
            byte[] bytes = await File.ReadAllBytesAsync(path);
            return Deserialize<T>(bytes);
        }
        
        //用于网络传输
        public static byte[] Serialize<T>(T message) where T : IMessage
            => message.ToByteArray();

        public static T Deserialize<T>(byte[] bytes) where T : class, IMessage, new()
        {
            T message = new();
            return (T)message.Descriptor.Parser.ParseFrom(bytes);
        }
        
        private static readonly Dictionary<Type, MessageParser> ParserCache = new();

        public static T DeserializeReflection<T>(byte[] bytes) where T : class, IMessage
        {
            var type = typeof(T);
            if (!ParserCache.TryGetValue(type, out var parser))
            {
                var parserProperty = type.GetProperty("Parser", BindingFlags.Public | BindingFlags.Static);
                if (parserProperty == null) return default;
                parser = parserProperty.GetValue(null) as MessageParser;
                if (parser != null)
                {
                    ParserCache[type] = parser;
                }
            }
            return parser?.ParseFrom(bytes) as T;
        }
    }
}
